<?php

namespace App\Services;

use App\Exceptions\ElasticSearchException;
use Elastic\Elasticsearch\Client;
use Elastic\Elasticsearch\Exception\ClientResponseException;
use Elastic\Elasticsearch\Exception\MissingParameterException;
use Elastic\Elasticsearch\Exception\ServerResponseException;
use Illuminate\Support\Facades\Cache;

/**
 * Service for interacting with Elasticsearch.
 */
readonly class ElasticsearchService implements SearchIndexServiceInterface
{
    /**
     * Create a new ElasticsearchService instance.
     *
     * @param Client $client The Elasticsearch client.
     */
    public function __construct(private Client $client)
    {
    }

    /**
     * Perform a search on the given index with the provided body.
     *
     * @param string $index The name of the index.
     * @param array $body The body of the search query.
     * @return array The hits from the search.
     *
     * @throws ElasticSearchException
     */
    public function search(string $index, array $body): array
    {
        $cacheKey = md5($index . json_encode($body));

        if (Cache::has($cacheKey)) {
            return Cache::get($cacheKey);
        }

        $params = [
            'index' => $index,
            'body' => $body
        ];

        try {
            $response = $this->client->search($params);
        } catch (ClientResponseException|ServerResponseException $exception) {
            throw new ElasticSearchException("Error while searching $index", 0, $exception);
        }

        $hits = $response['hits']['hits'];

        if (!empty($hits)) {
            Cache::put($cacheKey, $hits, 86400);
        }

        return $hits;
    }

    /**
     * Delete the given index.
     *
     * @param string $index The name of the index.
     *
     * @throws ElasticSearchException|MissingParameterException If an error occurs while deleting the index.
     */
    public function deleteIndex(string $index): void
    {
        $params = ['index' => $index];

        try {
            $this->client->indices()->delete($params);
        } catch (ClientResponseException|ServerResponseException $exception) {
            throw new ElasticSearchException("Error while deleting $index", 0, $exception);
        }
    }

    /**
     * Create an index with the given ID and body.
     *
     * @param string $index The name of the index.
     * @param string $id The ID of the document.
     * @param array $body The body of the document.
     *
     * @throws ElasticSearchException|MissingParameterException If an error occurs while creating the index.
     */
    public function createIndex(string $index, string $id, array $body): void
    {
        $params = [
            'index' => $index,
            'id' => $id,
            'body' => $body
        ];

        try {
            $this->client->index($params);
        } catch (ClientResponseException|ServerResponseException $exception) {
            throw new ElasticSearchException("Error while creating $index", 0, $exception);
        }
    }

    /**
     * Check if the given index exists.
     *
     * @param string $index The name of the index.
     * @return bool True if the index exists, false otherwise.
     *
     * @throws ServerResponseException If a server error occurs.
     * @throws ClientResponseException If a client error occurs.
     * @throws MissingParameterException If the index parameter is missing.
     */
    public function indexExists(string $index): bool
    {
        $params = ['index' => $index];
        $response = $this->client->indices()->exists($params);

        return $response->getStatusCode() === 200;
    }
}
